﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace SecurEntityLib
{
    class CryptoHelper
    {
        private static UInt32 currentVersion = 1;
        private static UInt32 secretLength = 32;
        private static UInt32 cipherBlockLength = 16;
        private static UInt32 aesKeyBits = 256;

        private UInt32 version;
        private byte[] cipherSalt;
        private byte[] hmacSalt;
        private byte[] IV;
        private byte[] hmacResult;

        private byte[] secretKey;
        private HMACSHA512 hmac512;
        private AesManaged aes;

        public CryptoHelper()
        {
        }

        public CryptoHelper(byte[] key, string securEntityData)
        {
            hmac512 = new HMACSHA512();

            MemoryStream ms = new MemoryStream(
                Convert.FromBase64String(securEntityData));
            BinaryReader br = new BinaryReader(ms);
            version = br.ReadUInt32();
            cipherSalt = br.ReadBytes((int)secretLength);
            hmacSalt = br.ReadBytes((int)secretLength);
            IV = br.ReadBytes((int)cipherBlockLength);
            hmacResult = br.ReadBytes(hmac512.HashSize / 8);

            secretKey = key;

            _DeriveHmac();
            _DeriveCipher();
        }

        private void _DeriveHmac()
        {
            hmac512 = new HMACSHA512(secretKey);
            hmac512.TransformBlock(
                hmacSalt,
                0,
                hmacSalt.Length,
                hmacSalt,
                0);
        }

        private void _DeriveCipher()
        {
            SHA512Managed sha = new SHA512Managed();
            sha.TransformBlock(secretKey, 0, secretKey.Length, secretKey, 0);
            sha.TransformFinalBlock(cipherSalt, 0, cipherSalt.Length);

            aes = new AesManaged();
            aes.KeySize = (int)aesKeyBits;
            aes.IV = IV;
            aes.Key = sha.Hash.Take(aes.Key.Length).ToArray();
            aes.Mode = CipherMode.CBC;
        }

        public void Initialize(byte[] key)
        {
            version = currentVersion;
            secretKey = key;
            cipherSalt = new byte[secretLength];
            hmacSalt = new byte[secretLength];
            IV = new byte[cipherBlockLength];

            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(cipherSalt);
            rng.GetBytes(hmacSalt);
            rng.GetBytes(IV);

            _DeriveHmac();
            _DeriveCipher();
        }

        public enum AddDataAction
        {
            Encrypt,
            Decrypt,
            DoNothing
        }

        public void AddData(
            byte[] data,
            AddDataAction action,
            bool final,
            ref string plaintext,
            ref string ciphertext)
        {
            //
            // Need to decrypt?
            //

            if (AddDataAction.Decrypt == action)
            {
                byte[] decoded = Convert.FromBase64String(ciphertext);
                ICryptoTransform decryptor = aes.CreateDecryptor();
                byte[] decrypted = decryptor.TransformFinalBlock(
                    decoded, 0, decoded.Length);

                data = decrypted;

                MemoryStream ms = new MemoryStream(decrypted);
                BinaryReader bw = new BinaryReader(ms);
                plaintext = bw.ReadString();
                bw.Close();
            }

            //
            // Add the property to integrity
            //

            if (false == final)
                hmac512.TransformBlock(
                    data, 0, data.Length, data, 0);
            else
                hmac512.TransformFinalBlock(data, 0, data.Length);

            //FileStream fs = File.Open("C:\\temp\\log.txt", FileMode.Append, FileAccess.Write);
            //fs.Write(data, 0, data.Length);
            //fs.Close();

            //
            // Need to encrypt?
            //

            if (AddDataAction.Encrypt == action)
            {
                ICryptoTransform encryptor = aes.CreateEncryptor();
                byte[] toEncode = encryptor.TransformFinalBlock(
                    data, 0, data.Length);
                ciphertext = Convert.ToBase64String(toEncode);
            }
        }

        public override string ToString()
        {
            MemoryStream ms = new MemoryStream();
            BinaryWriter bw = new BinaryWriter(ms);
            bw.Write(currentVersion);
            bw.Write(cipherSalt);
            bw.Write(hmacSalt);
            bw.Write(IV);
            bw.Write(hmac512.Hash);
            bw.Close();

            return Convert.ToBase64String(ms.ToArray());
        }

        public bool Verify()
        {
            return hmacResult.SequenceEqual(hmac512.Hash);
        }

        public static byte[] GetStringColumnValueThumbprint(
            byte[] secretKey,
            string colVal,
            string extraData)
        {
            HMACSHA512 hmac512 = new HMACSHA512(secretKey);

            //
            // Add the data
            //

            MemoryStream ms = new MemoryStream();
            BinaryWriter bw = new BinaryWriter(ms);
            bw.Write(colVal);
            bw.Write(extraData);
            bw.Close();

            hmac512.TransformFinalBlock(
                ms.ToArray(),
                0,
                ms.ToArray().Length);

            //
            // Return the hash
            //

            return hmac512.Hash;
        }
    }
}